package com.ly.nethub.netty.tcp

import com.ly.nethub.bean.IPInfo
import com.ly.nethub.config.ConnectStatus
import com.ly.nethub.utils.LogUtils
import com.ly.nethub.utils.TaskFunc
import io.netty.channel.Channel
import kotlinx.coroutines.delay
import kotlinx.coroutines.suspendCancellableCoroutine
import java.net.URI
import kotlin.coroutines.resume

internal class NettyTcpReconnectTask(
    private val netHub: TcpNetHub,
) {

    private val options get() = netHub.netHubOptions

    fun process(): TaskFunc<Unit> = {
        //重连时，停止心跳
        netHub.coroutineFactory.destroyWorkScope()
        while (!netHub.isClosed && netHub.mIsNetworkAvailable) {
            LogUtils.d("$TAG ReconnectTask 开始尝试连接")
            val connectStatus = connect()
            if (connectStatus == ConnectStatus.connected) {
                netHub.callbackNetHubConnectStatus(connectStatus)
                //连接成功，跳出循环
                break
            }
            //连接失败的情况
            if (connectStatus is ConnectStatus.ConnectFailed) {
                netHub.callbackNetHubConnectStatus(connectStatus)
                if (netHub.isClosed || !netHub.mIsNetworkAvailable) {
                    break
                }
                val timestamp = options.reconnectInterval * 2
                LogUtils.w("$TAG 一个周期连接失败，等待${timestamp}ms后再次尝试重连")
                delay(timestamp)
            }
        }
        //标识重连停止
        netHub.setReconnecting(false)
    }


    private suspend fun connect(): ConnectStatus {
        var connectStatus: ConnectStatus = ConnectStatus.failed
        val protocol = options.communicationProtocol
        val addressList = protocol.data
        val reconnectInterval = options.reconnectInterval
        val reconnectTimes = options.reconnectTimes
        when {
            netHub.isClosed -> connectStatus = ConnectStatus.closed
            !netHub.mIsNetworkAvailable -> connectStatus = ConnectStatus.networkUnavailable
            addressList.isEmpty() -> connectStatus = ConnectStatus.addressListEmpty
            else -> {
                netHub.initBootstrap()
                val isLessTwo = addressList.size < 2
                for (i in addressList.indices) {
                    val address = addressList[i]
                    val (host, port) = getHostInfo(address)
                    if ((isLessTwo) && (host.isEmpty() || port == -1)) {
                        connectStatus = ConnectStatus.serverIllegitimate
                        break
                    }
                    if (i == 0) {
                        netHub.callbackNetHubConnectStatus(ConnectStatus.connecting)
                    }
                    //这里加1是因为首次连接也相当于重连，所以假如reconnectTimes是2，则连接次数为3
                    val connectTimes = reconnectTimes + 1
                    for (time in 0 until connectTimes) {
                        if (netHub.isClosed) {
                            connectStatus = ConnectStatus.closed
                            break
                        }
                        if (!netHub.mIsNetworkAvailable) {
                            connectStatus = ConnectStatus.networkUnavailable
                            break
                        }
                        LogUtils.w("$TAG 正在进行【$address】第${time + 1}次连接")
                        val channel = tryConnectServer(host, port)
                        if (channel != null && channel.isOpen && channel.isActive && channel.isRegistered && channel.isWritable) {
                            netHub.setChannel(channel)
                            connectStatus = ConnectStatus.connected
                            break
                        } else {
                            if (time == reconnectTimes) {
                                //达到最大连接次数,但所有地址都连接失败
                                if (i == (addressList.size - 1)) {
                                    LogUtils.w("$TAG 【$address】连接失败")
                                    connectStatus = ConnectStatus.failed
                                    break
                                } else {
                                    //否则，无需回调连接失败，等待一段时间再进行下一个服务器地址连接即可
                                    LogUtils.w("$TAG 【$address】连接失败，正在等待下一个服务器地址的重连，当前重连时长:${netHub.netHubOptions.reconnectInterval}")
                                    LogUtils.w("$TAG =============================================================")
                                    delay(reconnectInterval)
                                }
                            } else {
                                //连接失败，重连时长(interval / 2 * n )ms
                                val timestamp =
                                    (reconnectInterval + reconnectInterval * 1.0f / 2 * time).toLong()
                                LogUtils.w("$TAG 【$address】连接失败，正在等待重连，当前重连延时时长:$timestamp")
                                delay(timestamp)
                            }
                        }
                    }
                }
            }
        }

        return connectStatus
    }

    /**
     * 目前只有两种协议TCP 和WebSocket
     * @param address List<String> ,List<IPInfo>
     * @return pair first host,second port
     */
    private fun getHostInfo(address: Any): Pair<String, Int> {
        return when (address) {
            is String -> {
                val uri = try {
                    URI.create(address)
                } catch (e: Exception) {
                    e.printStackTrace()
                    null
                }
                (uri?.host ?: "") to (uri?.port ?: -1)
            }

            is IPInfo -> {
                if (address.isLegalPort && address.isLegalIP) {
                    address.ip to address.port
                } else {
                    "" to -1
                }
            }

            else -> "" to -1
        }
    }

    private suspend fun tryConnectServer(host: String, port: Int): Channel? =
        suspendCancellableCoroutine {
            try {
                val channelFuture = netHub.mBootstrap?.connect(host, port)?.sync()
                if (channelFuture == null) {
                    it.resume(null)
                } else {
                    val channel = channelFuture.channel()
                    channelFuture.addListener { future ->
                        if (future.isSuccess) {
                            LogUtils.w("$TAG host:$host,port:$port,连接成功")
                            it.resume(channel)
                        } else {
                            LogUtils.w("$TAG host:$host,port:$port,连接失败，cause:${future.cause()}")
                            it.resume(null)
                        }
                    }
                }
            } catch (e: Exception) {
                LogUtils.d("$TAG tryConnectServer 异常($e)")
                it.resume(null)
            }
        }

    companion object {
        private const val TAG = "NettyTCPReconnectTask"
    }
}